package pzx

import nancal.mp.TestBase
import nbcp.base.extend.*
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory
import java.io.File
import java.util.function.Consumer

/**
 * StyleProcTest
 * @author yuxh
 */

class StyleProcTest : TestBase() {
    companion object {
        private const val S_DOT_JAVA = ".java"
        private const val S_DOT_GIT = ".git"
        private const val S_TARGET = "target"
        private val log = LoggerFactory.getLogger(this::class.java.declaringClass);
    }

    private fun recursionFiles(consumer: Consumer<File>) {
        File("D:\\boe\\iam-api").ListRecursionFiles({ directory ->
            var pathName = directory.FullName;
            if (S_DOT_GIT == pathName) {
                return@ListRecursionFiles false
            }
            if (S_TARGET == pathName && !pathName.contains(File.separator + "src" + File.separator)) {
                return@ListRecursionFiles false
            }
            true
        }, { file: File ->
            var fileName = file.FullName;
            if (!fileName.endsWith(S_DOT_JAVA)) {
                return@ListRecursionFiles true
            }
            consumer.accept(file)
            true
        });
    }

    private fun procLines(file: File, callback: (MutableList<String>, Int, String) -> Unit) {
        val content = file.readText()
        val lines = content.split(System.lineSeparator().toRegex())
            .dropLastWhile { it.isEmpty() }
            .toMutableList();

        var index = -1
        while (index < lines.size - 1) {
            index++
            val line = lines.get(index)

            callback.invoke(lines, index, line);
        }

        val content2: String =
            java.lang.String.join(System.lineSeparator(), lines) + System.lineSeparator()
        if (content == content2) {
            return
        }
        log.warn(file.path)
        file.writeText(content2);
    }

    @Test
    fun checkEndEmptyLine() {
        recursionFiles { file: File -> procEndEmptyLine(file) }
    }

    @Test
    fun checkLine120Chars() {
        recursionFiles { file: File -> procLine120Chars(file) }
    }

    @Test
    fun checkPlus() {
        recursionFiles { file: File -> procPlus(file, "+") }
        recursionFiles { file: File -> procPlus(file, "&&") }
        recursionFiles { file: File -> procPlus(file, "||") }
    }

    @Test
    fun checkDoc() {
        recursionFiles { file: File -> procLines(file, this::procDocReturn) }
        recursionFiles { file: File -> procLines(file, this::procDocComment) }
    }

    @Test
    fun checkLombok() {
        recursionFiles { file: File -> procLines(file, this::procLombok) }
    }

    @Test
    fun checkStyle() {
        recursionFiles { file: File -> procLines(file, this::procStyle) }
    }

    private fun procStyle(lines: MutableList<String>, index: Int, line: String) {
        if (!line.endsWith("){")) {
            return;
        }

        lines.set(index, line.Slice(0, -2) + ") {")
    }

    private fun procLombok(lines: MutableList<String>, index: Int, line: String) {
        if (line == "import lombok.EqualsAndHashCode;") {
            if (!lines.any { it -> it.startsWith("@EqualsAndHashCode(") }) {
                lines.removeAt(index);
            }
        }
    }

    /**
     *
     */
    private fun procDocComment(lines: MutableList<String>, index: Int, line: String) {
        if (line.trim() != "/**") {
            return;
        }

        if (index >= lines.size - 3) {
            return;
        }

        var endIndex = lines.Slice(index)
            .indexOfFirst { it.trim() == "*/" }

        if (endIndex < 0) {
            return;
        }

        var line2 = lines.get(index + 1);

        if (line2.trim() == "*") {
            var type = getDocMethodName(lines, endIndex + index);
            if (type.HasValue) {
                lines.set(index + 1, line2 + " " + type)
            }
        } else if (line2.trim().startsWith("* @")) {
            var type = getDocMethodName(lines, endIndex + index);
            if (type.HasValue) {
                lines.InsertAfter(index, getStartBlanks(line2) + "* " + type)
            }
        }
    }

    private fun getStartBlanks(line2: String): String {
        var index = line2.indexOfFirst { it != ' ' }
        return " ".Repeat(index);
    }

    private fun getDocMethodName(lines: MutableList<String>, index: Int): String {
        if (index >= lines.size - 1) {
            return "";
        }
        var nextLine2 = lines.get(index).trim();

        if (nextLine2.isEmpty() || nextLine2.startsWith("*")) {
            return getDocMethodName(lines, index + 1);
        }

        if (nextLine2.startsWith("@")) {
            return getDocMethodName(lines, index + 1);
        }

        if (nextLine2.endsWith("+")) {
            return getDocMethodName(lines, index + 1);
        }

        if (nextLine2.startsWith("+")) {
            return getDocMethodName(lines, index + 1);
        }

        if (nextLine2.equals(")")) {
            return getDocMethodName(lines, index + 1);
        }

        if (nextLine2.startsWith("public ") ||
            nextLine2.startsWith("default ") ||
            nextLine2.startsWith("private ")
        ) {
            var types = Regex("\\s+").split(nextLine2).dropWhile {
                it == "public" ||
                        it == "private" ||
                        it == "default" ||
                        it == "static" ||
                        it == "class" ||
                        it == "interface" ||
                        it == "final"
            };

            if (types.size > 1) {
                var type = types.get(1);
                var match = Regex("^([\\d\\w]+).*").matchEntire(type);
                if (match != null) {
                    return match.groupValues.get(1);
                }
            }
            return "";
        }

        var types = Regex("\\s+").split(nextLine2).dropWhile {
            it == "public" ||
                    it == "private" ||
                    it == "default" ||
                    it == "static" ||
                    it == "class" ||
                    it == "interface" ||
                    it == "final"
        }

        if (types.size > 1) {
            var type = types.get(1);
            var match = Regex("^([\\d\\w]+).*").matchEntire(type);
            if (match != null) {
                return match.groupValues.get(1);
            }
        }
        return "";
    }

    private fun procDocReturn(lines: MutableList<String>, index: Int, line: String) {
        if (line.trim() != "* @return") {
            return;
        }

        if (index >= lines.size - 3) {
            return;
        }

        var endIndex = lines.Slice(index).indexOfFirst { it.trim() == "*/" }

        if (endIndex < 0) {
            return;
        }

        var type = getPublicReturnType(lines, endIndex + index);
        if (type.HasValue) {
            lines.set(index, line + " " + type)
        }
    }

    private fun getPublicReturnType(lines: MutableList<String>, index: Int): String {
        if (index >= lines.size - 1) {
            return "";
        }
        var nextLine2 = lines.get(index).trim();

        if (nextLine2.isEmpty() || nextLine2.startsWith("*")) {
            return getPublicReturnType(lines, index + 1);
        }

        if (nextLine2.startsWith("@")) {
            return getPublicReturnType(lines, index + 1);
        }
        if (nextLine2.startsWith("public ") ||
            nextLine2.startsWith("default ") ||
            nextLine2.startsWith("private ")
        ) {
            var types = Regex("\\s+").split(nextLine2).dropWhile {
                it == "public" ||
                        it == "private" ||
                        it == "default" ||
                        it == "static" ||
                        it == "final"
            };

            if (types.any()) {
                var type = types.get(0);
                if (type == "void") {
                    return "";
                }
                return type;
            }
            return "";
        }

        var types = Regex("\\s+").split(nextLine2).dropWhile {
            it == "public" ||
                    it == "private" ||
                    it == "default" ||
                    it == "static" ||
                    it == "final"
        };

        if (types.any()) {
            var type = types.get(0);
            if (type == "void") {
                return "";
            }
            return type;
        }
        return "";
    }

    private fun procPlus(file: File, newLine: String) {
        val content = file.readText()
        val lines = content.split(System.lineSeparator().toRegex())
            .dropLastWhile { it.isEmpty() }
            .toMutableList();
        var index = -1
        while (index < lines.size - 1) {
            index++
            val line = lines.get(index)
            if (!line.trim().endsWith(newLine)) {
                index++
                continue
            }
            if (line.trim().equals(newLine)) {
                continue
            }
            lines.set(index, line.substring(0, line.length - newLine.length))
            val nextLine = lines.get(index + 1)
            if (nextLine.trim().startsWith("//")
                || nextLine.trim().startsWith("/*")
                || nextLine.isEmpty()
            ) {
                lines.add(index + newLine.length, newLine)
                index++
                index++
                continue
            }
            val firstCharIndex = getFirstCharIndex(nextLine)
            lines.set(
                index + 1, nextLine.substring(0, firstCharIndex) + newLine + " " + nextLine
                    .substring(firstCharIndex)
            )
            index++
        }
        val content2: String =
            java.lang.String.join(System.lineSeparator(), lines) + System.lineSeparator()
        if (content == content2) {
            return
        }
        log.warn(file.path)
        file.writeText(content2)
    }

    private fun getFirstCharIndex(nextLine: String): Int {
        if (nextLine.isEmpty()) {
            return -1
        }
        for (i in 0 until nextLine.length) {
            val item = nextLine[i]
            if (item == ' ') {
                continue
            }
            return i
        }
        return -1
    }

    private fun procLine120Chars(file: File) {
        val lines = file.readText().split(System.lineSeparator())
        var index = 0
        for (line in lines) {
            index++
            if (line.length > 120) {
                log.warn(file.path + ":" + index + System.lineSeparator() + line)
            }
        }
    }

    private fun procEndEmptyLine(file: File) {
        val lines = file.readText().split(System.lineSeparator()).toMutableList()
        val endLine = lines.last()
        if (endLine.isNullOrEmpty()) {
            return
        }
        lines.add("")
        file.writeText(java.lang.String.join(System.lineSeparator(), lines))
    }
}